#include <stm32l4xx.h>

#include <linux/dev/serial_core.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/container_of.h>

struct stm32_port
{
    struct uart_port port;
    UART_HandleTypeDef handle;
    IRQn_Type irqn;
};

static inline struct stm32_port *to_stm32_port(struct uart_port *port)
{
    return container_of(port, struct stm32_port, port);
}

static void __uart_handle_init(UART_HandleTypeDef *uh)
{
    uh->Init.BaudRate = 115200;
    uh->Init.HwFlowCtl = UART_HWCONTROL_NONE;
    uh->Init.Mode = UART_MODE_TX_RX;
    uh->Init.OverSampling = UART_OVERSAMPLING_16;
    uh->Init.WordLength = UART_WORDLENGTH_8B;
    uh->Init.StopBits = UART_STOPBITS_1;
    uh->Init.Parity = UART_PARITY_NONE;

    HAL_UART_Init(uh);
}

static int _uart_startup(struct uart_port *port)
{
    struct stm32_port *uart = to_stm32_port(port);

    __uart_handle_init(&uart->handle);

    __HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_RXNE);
    HAL_NVIC_EnableIRQ(uart->irqn);

    return 0;
}

static void _uart_start_tx(struct uart_port *port)
{
    struct stm32_port *uart = to_stm32_port(port);

    __HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_TXE);
}

static void _uart_stop_tx(struct uart_port *port)
{
    struct stm32_port *uart = to_stm32_port(port);

    __HAL_UART_DISABLE_IT(&(uart->handle), UART_IT_TXE);
}

static struct stm32_port _uart_ports[1];
void USART1_IRQHandler(void)
{
    struct stm32_port *uart = &_uart_ports[0];

    if ((__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET) &&
        (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_RXNE) != RESET))
    {
        uint8_t ch = uart->handle.Instance->RDR;

        uart_received_char(&uart->port, ch);
    }

    if ((__HAL_UART_GET_FLAG(&uart->handle, UART_FLAG_TXE) != RESET) &&
        (__HAL_UART_GET_IT_SOURCE(&uart->handle, UART_IT_TXE) != RESET))
    {
        uint8_t c;

        if (uart_fifo_get(&uart->port, &c))
        {
            uart->handle.Instance->TDR = c;
        }
        else
        {
            uart_tx_stop(&uart->port);
        }

        uart_notify_writeable(&uart->port);
    }

    if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_ORE) != RESET)
    {
        __HAL_UART_CLEAR_OREFLAG(&uart->handle);
    }
}

static struct uart_driver _uart_driver = {
    .nr = 1,
};

static const struct uart_ops _uart_ops = {
    .startup = _uart_startup,
    .start_tx = _uart_start_tx,
    .stop_tx = _uart_stop_tx,
};

static int drv_uart_init(void)
{
    struct stm32_port *uart = &_uart_ports[0];

    uart->handle.Instance = USART1;
    uart->irqn = USART1_IRQn;

    uart_register_driver(&_uart_driver);
    uart->port.ops = &_uart_ops;
    uart_add_one_port(&_uart_driver, &uart->port);

    return 0;
}
device_initcall(drv_uart_init);
